/* $Id$ */
/*
 * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
 * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */
#include <pjmedia/mcx_stream.h>
#include <pjmedia/errno.h>
#include <pjmedia/rtp.h>
#include <pjmedia/rtcp.h>
#include <pjmedia/jbuf.h>
#include <pjmedia/stream_common.h>
#include <pj/array.h>
#include <pj/assert.h>
#include <pj/ctype.h>
#include <pj/compat/socket.h>
#include <pj/errno.h>
#include <pj/ioqueue.h>
#include <pj/log.h>
#include <pj/os.h>
#include <pj/pool.h>
#include <pj/rand.h>
#include <pj/sock_select.h>
#include <pj/string.h> /* memcpy() */

#define THIS_FILE "mcx_stream.c"
#define ERRLEVEL 1
#define LOGERR_(expr) PJ_PERROR(4, expr);
#define TRC_(expr) PJ_LOG(5, expr)

#define BYTES_PER_SAMPLE 2

/* Limit the number of synthetic audio samples that are generated by PLC.
 * Normally PLC should have it's own means to limit the number of
 * synthetic frames, so we need to set this to a reasonably large value
 * just as precaution
 */
#define MAX_PLC_MSEC PJMEDIA_MAX_PLC_DURATION_MSEC

/* Tracing jitter buffer operations in a stream session to a CSV file.
 * The trace will contain JB operation timestamp, frame info, RTP info, and
 * the JB state right after the operation.
 */

#ifndef PJMEDIA_STREAM_SIZE
#define PJMEDIA_STREAM_SIZE 1000
#endif

#ifndef PJMEDIA_STREAM_INC
#define PJMEDIA_STREAM_INC 1000
#endif

/**
 * This structure describes media stream.
 * A media stream is bidirectional media transmission between two endpoints.
 * It consists of two channels, i.e. encoding and decoding channels.
 * A media stream corresponds to a single "m=" line in a SDP session
 * description.
 */
struct pjmedia_mcx_stream
{
	pjmedia_endpt *endpt;		  /**< Media endpoint.	    */
	pjmedia_mcx_stream_info si;		  /**< Creation parameter.        */
	pj_pool_t *own_pool; /**< Only created if not given  */

	pjmedia_dir dir; /**< Stream direction.	    */
	void *user_data; /**< User data.		    */
	pj_str_t cname;	 /**< SDES CNAME		    */

	pjmedia_transport *transport; /**< Stream transport.	    */

	void (*floor_cb)(pjmedia_mcx_stream *, void *, int, pj_str_t *info); /*lihongpu add*/
	void *dtmf_cb_user_data;

	pj_sockaddr rem_rtp_addr; /**< Remote RTP address	    */
	unsigned rtp_src_cnt;	  /**< How many pkt from
				   this addr.   	    */
};

/* Zero audio frame samples */
static pj_int16_t zero_frame[2 * 30 * 16000 / 1000];

static void on_rx_rtcp(void *data,
					   void *pkt,
					   pj_ssize_t bytes_read);

static pj_status_t send_rtcp(pjmedia_mcx_stream *stream,
							 pj_bool_t with_sdes,
							 pj_bool_t with_bye,
							 pj_bool_t with_xr,
							 pj_bool_t with_fb);

/*
 * play_callback()
 *
 * This callback is called by sound device's player thread when it
 * needs to feed the player with some frames.
 */
static pj_status_t get_frame(pjmedia_port *port, pjmedia_frame *frame)
{
	return PJ_SUCCESS;
}

/* The other version of get_frame callback used when stream port format
 * is non linear PCM.
 */
static pj_status_t get_frame_ext(pjmedia_port *port, pjmedia_frame *frame)
{
	return PJ_SUCCESS;
}

static pj_status_t send_rtcp(pjmedia_mcx_stream *stream,
							 pj_bool_t with_sdes,
							 pj_bool_t with_bye,
							 pj_bool_t with_xr,
							 pj_bool_t with_fb)
{
	return PJ_SUCCESS;
}

/**
 * put_frame_imp()
 */
static pj_status_t put_frame_imp(pjmedia_port *port,
								 pjmedia_frame *frame)
{
	return PJ_SUCCESS;
}

/**
 * put_frame()
 *
 * This callback is called by upstream component when it has PCM frame
 * to transmit. This function encodes the PCM frame, pack it into
 * RTP packet, and transmit to peer.
 */
static pj_status_t put_frame(pjmedia_port *port,
							 pjmedia_frame *frame)
{
	return PJ_SUCCESS;
}


/*
 * This callback is called by stream transport on receipt of packets
 * in the RTP socket.
 */
static void on_rx_rtp(void *data,
					   void *pkt,
					   pj_ssize_t bytes_read)
{
	PJ_LOG(4, (THIS_FILE, "on_rx_rtp bytes_read = %d",bytes_read));
	on_rx_rtcp(data,pkt,bytes_read);
}

/*
 * This callback is called by stream transport on receipt of packets
 * in the RTCP socket.
 */
static void on_rx_rtcp(void *data,
					   void *pkt,
					   pj_ssize_t bytes_read)
{
	pjmedia_mcx_stream *stream = (pjmedia_mcx_stream *)data;
	pj_status_t status;
	pj_bool_t result = PJ_FALSE;
	PJ_LOG(4, (THIS_FILE, "on_rx_rtcp bytes_read = %d",bytes_read));
	/* Check for errors */
	if (bytes_read < 0)
	{
		status = (pj_status_t)-bytes_read;
		if (status == PJ_STATUS_FROM_OS(OSERR_EWOULDBLOCK))
		{
			return;
		}
		return;
	}

	// char *pChar = pkt;

	// char val0 = *pChar;
	// char val1 = *(pChar + 1);
	// char val2 = *(pChar + 2);
	// char val3 = *(pChar + 3);

	// char val4 = *(pChar + 4);
	// char val5 = *(pChar + 5);
	// char val6 = *(pChar + 6);
	// char val7 = *(pChar + 7);

	// char val8 = *(pChar + 8);
	// char val9 = *(pChar + 9);
	// char val10 = *(pChar + 10);
	// char val11 = *(pChar + 11);

	// PJ_LOG(4, (THIS_FILE, "on_rx_rtcp  val0 = %x,val1 = %x,val2 = %x,val3 = %x", val0, val1, val2, val3));
	// PJ_LOG(4, (THIS_FILE, "on_rx_rtcp  val4 = %x,val5 = %x,val6 = %x,val7 = %x", val4, val5, val6, val7));
	// PJ_LOG(4, (THIS_FILE, "on_rx_rtcp  va8 = %x,val9 = %x,val10 = %x,val11 = %x", val8, val9, val10, val11));

	/*lihongpu add begin*/
	result = is_rtcp_app(pkt, bytes_read);
	PJ_LOG(4, (THIS_FILE, "on_rx_rtcp result = %d",result));
	if (result == PJ_TRUE)
	{
		char msgStr[2048];
		memset(msgStr, '\0', sizeof(msgStr));

		pjmedia_rtcp_rx_rtcp_app(&stream->transport, pkt, bytes_read, msgStr);
		if (stream->floor_cb && strlen(msgStr) != 0)
		{
			const pj_str_t text = pj_str(msgStr);
			stream->floor_cb(stream, stream->dtmf_cb_user_data, 1, &text);
		}
		else
		{
			PJ_LOG(4, (THIS_FILE, "on_rx_rtcp stream->floor_cb null "));
		}
	}
	else
	{
		// pjmedia_rtcp_rx_rtcp(&stream->rtcp, pkt, bytes_read);
	}
	/*lihongpu add end*/
}

/*
 * Handle events.
 */
static pj_status_t stream_mcx_event_cb(pjmedia_event *event,
								   void *user_data)
{
	return PJ_SUCCESS;
}

/*
 * Create media stream.
 */
PJ_DEF(pj_status_t)
pjmedia_mcx_stream_create(pjmedia_endpt *endpt,
						  pj_pool_t *pool,
						  const pjmedia_mcx_stream_info *info,
						  pjmedia_transport *tp,
						  void *user_data,
						  pjmedia_mcx_stream **p_stream)

{
	PJ_LOG(4, (THIS_FILE, "pjmedia_mcx_stream_create info->dir = %d",info->dir));
	enum
	{
		M = 32
	};
	pjmedia_mcx_stream *stream;
	pj_str_t name;
	unsigned jb_init, jb_max, jb_min_pre, jb_max_pre;
	pjmedia_audio_format_detail *afd;
	pj_pool_t *own_pool = NULL;
	char *p;
	pj_status_t status;
	pjmedia_transport_attach_param att_param;

	PJ_ASSERT_RETURN(endpt && info && p_stream, PJ_EINVAL);

	if (pool == NULL)
	{
		own_pool = pjmedia_endpt_create_pool(endpt, "strm%p",
											 PJMEDIA_STREAM_SIZE,
											 PJMEDIA_STREAM_INC);
		PJ_ASSERT_RETURN(own_pool != NULL, PJ_ENOMEM);
		pool = own_pool;
	}

	/* Allocate the media stream: */

	stream = PJ_POOL_ZALLOC_T(pool, pjmedia_mcx_stream);
	PJ_ASSERT_RETURN(stream != NULL, PJ_ENOMEM);
	stream->own_pool = own_pool;

	/* Duplicate stream info */
	pj_memcpy(&stream->si, info, sizeof(*info));
	// pj_strdup(pool, &stream->si.fmt.encoding_name, &info->fmt.encoding_name);

	/* Init stream/port name */
	name.ptr = (char *)pj_pool_alloc(pool, M);
	name.slen = pj_ansi_snprintf(name.ptr, M, "strm%p", stream);
	PJ_LOG(4,(THIS_FILE, "pjmedia_mcx_stream_create name =  %.*s",name.slen, name.ptr));

	/* Init stream: */
	stream->endpt = endpt;
	// stream->codec_mgr = pjmedia_endpt_get_codec_mgr(endpt);
	stream->dir = info->dir;
	stream->user_data = user_data;
	// stream->rtcp_interval = (PJMEDIA_RTCP_INTERVAL - 500 + (pj_rand() % 1000)) *
	// 						info->fmt.clock_rate / 1000;
	// stream->rtcp_sdes_bye_disabled = info->rtcp_sdes_bye_disabled;

	PJ_LOG(4,(THIS_FILE, "pjmedia_mcx_stream_create a"));
	stream->cname = info->cname;
	if (stream->cname.slen == 0)
	{
		/* Build random RTCP CNAME. CNAME has user@host format */
		stream->cname.ptr = p = (char *)pj_pool_alloc(pool, 20);
		pj_create_random_string(p, 5);
		p += 5;
		*p++ = '@';
		*p++ = 'p';
		*p++ = 'j';
		pj_create_random_string(p, 6);
		p += 6;
		*p++ = '.';
		*p++ = 'o';
		*p++ = 'r';
		*p++ = 'g';
		stream->cname.slen = p - stream->cname.ptr;
	}
	PJ_LOG(4,(THIS_FILE, "pjmedia_mcx_stream_create stream->cname =  %.*s",stream->cname.slen, stream->cname.ptr));

	PJ_LOG(4,(THIS_FILE, "pjmedia_mcx_stream_create 0"));
	pj_bzero(&att_param, sizeof(att_param));
	PJ_LOG(4,(THIS_FILE, "pjmedia_mcx_stream_create 1"));
	att_param.stream = stream;
	att_param.media_type = PJMEDIA_TYPE_APPLICATION;
	att_param.user_data = stream;
	pj_sockaddr_cp(&att_param.rem_addr, &info->rem_addr);
	pj_sockaddr_cp(&stream->rem_rtp_addr, &info->rem_addr);
	PJ_LOG(4,(THIS_FILE, "pjmedia_mcx_stream_create att_param.rem_addr.ipv4.sin_port = %d ",att_param.rem_addr.ipv4.sin_port));
	pj_sockaddr_cp(&att_param.rem_rtcp, &info->rem_addr);
	// if (stream->si.rtcp_mux)
	// {
	// 	pj_sockaddr_cp(&att_param.rem_rtcp, &info->rem_addr);
	// }
	// else if (pj_sockaddr_has_addr(&info->rem_rtcp.addr))
	// {
	// 	pj_sockaddr_cp(&att_param.rem_rtcp, &info->rem_addr);
	// }
	att_param.addr_len = pj_sockaddr_get_len(&info->rem_addr);
	att_param.rtp_cb = &on_rx_rtp;
	att_param.rtcp_cb = &on_rx_rtcp;
	PJ_LOG(4,(THIS_FILE, "pjmedia_mcx_stream_create 3"));
	/* Only attach transport when stream is ready. */
	status = pjmedia_transport_attach2(tp, &att_param);
	if (status != PJ_SUCCESS)
		goto err_cleanup;

	stream->transport = tp;


	/* Success! */
	*p_stream = stream;

	PJ_LOG(4, (THIS_FILE, "pjmedia_mcx_stream_create Stream stream->cname = %.*s created", stream->cname.slen,stream->cname.ptr));

	return PJ_SUCCESS;

err_cleanup:
	pjmedia_mcx_stream_destroy(stream);
	return status;
}

/*
 * Destroy stream.
 */
PJ_DEF(pj_status_t)
pjmedia_mcx_stream_destroy(pjmedia_mcx_stream *stream)
{
	pj_status_t status;
	PJ_LOG(4, (THIS_FILE, "pjmedia_mcx_stream_destroy Stream stream->cname = %.*s created", stream->cname.slen,stream->cname.ptr));

	PJ_ASSERT_RETURN(stream != NULL, PJ_EINVAL);

	/* Detach from transport
	 * MUST NOT hold stream mutex while detaching from transport, as
	 * it may cause deadlock. See ticket #460 for the details.
	 */
	if (stream->transport)
	{
		pjmedia_transport_detach(stream->transport, stream);
		stream->transport = NULL;
	}

	pj_pool_safe_release(&stream->own_pool);

	return PJ_SUCCESS;
}

/*lihongpu add begin*/
pj_status_t mcx_rtp_session_send_rtcp_APP(pjmedia_transport *tp, pj_uint8_t subtype, const char *name, const pj_uint8_t *data, int datalen, pj_uint32_t ssrc)
{

	if (tp == NULL)
	{
		PJ_LOG(4, (THIS_FILE, "mcx_rtp_session_send_rtcp_APP tp == NULL "));
		return PJ_EUNKNOWN;
	}
	if (name == NULL)
	{
		PJ_LOG(4, (THIS_FILE, "mcx_rtp_session_send_rtcp_APP name == NULL "));
		return PJ_EUNKNOWN;
	}
	if (data == NULL)
	{
		PJ_LOG(4, (THIS_FILE, "mcx_rtp_session_send_rtcp_APP data == NULL "));
		return PJ_EUNKNOWN;
	}
	PJ_LOG(4, (THIS_FILE, "mcx_rtp_session_send_rtcp_APP ssrc = %04x ", ssrc));
	pj_status_t status = PJ_SUCCESS;

	pjmedia_rtcp_app_t app;
	app.common.version = 2;
	app.common.p = 0;
	app.common.pt = 204;
	app.common.count = subtype;
	pj_uint16_t size = (sizeof(app) + datalen) / 4 - 1;
	// PJ_LOG(4, (THIS_FILE, "mcx_rtp_session_send_rtcp_APP size == %d ", size));
	// PJ_LOG(4, (THIS_FILE, "mcx_rtp_session_send_rtcp_APP pj_ntohl(size) == %d ", pj_ntohl(size)));
	app.common.length = pj_htons(size);
	app.common.ssrc = pj_ntohl(ssrc);

	memset(app.name, 0, 4);
	memcpy(app.name, name, 4);

	pj_uint8_t pkt[500] = {0};
	memcpy(pkt, &app, sizeof(app));
	memcpy(pkt + sizeof(app), data, datalen);

	/*PJ_LOG(4, (THIS_FILE, "mcx_rtp_session_send_rtcp_APP datalen == %d ", datalen));
	PJ_LOG(4, (THIS_FILE, "mcx_rtp_session_send_rtcp_APP sizeof(app) == %d ", sizeof(app)));
	PJ_LOG(4, (THIS_FILE, "mcx_rtp_session_send_rtcp_APP sizeof(app) + datalen == %d ", sizeof(app) + datalen));*/
	status = pjmedia_transport_send_rtcp(tp, (const void *)pkt, sizeof(app) + datalen);
	return status;
}
/*lihongpu add end*/

/*
 * Start stream.
 */
PJ_DEF(pj_status_t)
pjmedia_mcx_stream_start(pjmedia_mcx_stream *stream)
{
	PJ_LOG(4, (THIS_FILE, "pjmedia_mcx_stream_start Stream stream->cname = %.*s created", stream->cname.slen,stream->cname.ptr));

	return PJ_SUCCESS;
}

PJ_DEF(pj_status_t)
pjmedia_mcx_stream_get_info(const pjmedia_mcx_stream *stream,
							pjmedia_mcx_stream_info *info)
{
	PJ_ASSERT_RETURN(stream && info, PJ_EINVAL);

	pj_memcpy(info, &stream->si, sizeof(pjmedia_mcx_stream_info));
	return PJ_SUCCESS;
}

/*
 * Get stream statistics.
 */
PJ_DEF(pj_status_t)
pjmedia_mcx_stream_get_stat(const pjmedia_mcx_stream *stream,
							pjmedia_rtcp_stat *stat)
{
	PJ_ASSERT_RETURN(stream && stat, PJ_EINVAL);

	// pj_memcpy(stat, &stream->rtcp.stat, sizeof(pjmedia_rtcp_stat));
	return PJ_SUCCESS;
}

/*
 * Reset the stream statistics in the middle of a stream session.
 */
PJ_DEF(pj_status_t)
pjmedia_mcx_stream_reset_stat(pjmedia_mcx_stream *stream)
{
	PJ_ASSERT_RETURN(stream, PJ_EINVAL);
	// pjmedia_rtcp_init_stat(&stream->rtcp.stat);
	return PJ_SUCCESS;
}

/*
 * Pause stream.
 */
PJ_DEF(pj_status_t)
pjmedia_mcx_stream_pause(pjmedia_mcx_stream *stream,
						 pjmedia_dir dir)
{
	PJ_ASSERT_RETURN(stream, PJ_EINVAL);
	return PJ_SUCCESS;
}

/*
 * Resume stream
 */
PJ_DEF(pj_status_t)
pjmedia_mcx_stream_resume(pjmedia_mcx_stream *stream,
						  pjmedia_dir dir)
{
	PJ_ASSERT_RETURN(stream, PJ_EINVAL);
	return PJ_SUCCESS;
}

/*lihongpu add begin*/
/*
 * Set callback to be called upon receiving DTMF digits.
 */
PJ_DEF(pj_status_t)
pjmedia_mcx_stream_set_floor_callback(pjmedia_mcx_stream *stream,
									  void (*cb)(pjmedia_mcx_stream *,
												 void *user_data,
												 int digit, pj_str_t *floor_info),
									  void *user_data)
{
	PJ_ASSERT_RETURN(stream, PJ_EINVAL);
	PJ_LOG(4, (THIS_FILE, "pjmedia_stream_set_floor_callback"));

	/* By convention, we use jitter buffer's mutex to access DTMF
	 * digits resources.
	 */
	// pj_mutex_lock(stream->jb_mutex);

	stream->floor_cb = cb;
	stream->dtmf_cb_user_data = user_data;

	// pj_mutex_unlock(stream->jb_mutex);

	return PJ_SUCCESS;
}
/*lihongpu add end*/

/**
 * Get RTP session information from stream.
 */
PJ_DEF(pj_status_t)
pjmedia_mcx_stream_get_rtp_session_info(pjmedia_mcx_stream *stream,
										pjmedia_stream_rtp_sess_info *session_info)
{
	// session_info->rtcp = &stream->rtcp;
	return PJ_SUCCESS;
}
